package com.ilikesoup.instant.inclusion;

import com.google.common.collect.Sets;
import com.ilikesoup.instant.empty.EmptyUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiPredicate;

public class Inclusion<T> {

    private final Set<T> includes;

    private final Set<T> excludes;

    private final Mode mode;

    public enum Mode {
        /**
         *  include作为大范围，排除其中的exclude项
         */
        INCLUDE,
        /**
         *  exclude作为大范围，排除其中的include项
         */
        EXCLUDE
    }

    public Inclusion() {
        this(Mode.INCLUDE);
    }

    public Inclusion(Mode mode) {
        this.includes = Sets.newLinkedHashSet();
        this.excludes = Sets.newLinkedHashSet();
        this.mode = Objects.requireNonNull(mode);
    }

    public Inclusion(Collection<T> includes, Collection<T> excludes, Mode mode) {
        this.includes = Sets.newLinkedHashSet(Objects.requireNonNull(includes));
        this.excludes = Sets.newLinkedHashSet(Objects.requireNonNull(excludes));
        this.mode = Objects.requireNonNull(mode);
    }

    public <V> boolean isIncluded(V target, BiPredicate<T, V> tester) {
        switch (mode) {
            case INCLUDE:
                for (T include : includes) {
                    if(tester.test(include, target)) {
                        for (T exclude : excludes) {
                            if(tester.test(exclude, target)) {
                                return false;
                            }
                        }
                        return true;
                    }
                }
                return false;
            case EXCLUDE:
                for (T exclude : excludes) {
                    if(tester.test(exclude, target)) {
                        for (T include : includes) {
                            if(tester.test(include, target)) {
                                return true;
                            }
                        }
                        return false;
                    }
                }
                return true;
            default:
                return false;
        }
    }

    public Inclusion<T> addIncludes(T... includes) {
        if(!EmptyUtils.isEmpty(includes)) {
            for (T include : includes) {
                this.includes.add(include);
            }
        }
        return this;
    }

    public Inclusion<T> addIncludes(Collection<T> includes) {
        if(!EmptyUtils.isEmpty(includes)) {
            this.includes.addAll(includes);
        }
        return this;
    }

    public Inclusion<T> addExcludes(T... excludes) {
        if(!EmptyUtils.isEmpty(excludes)) {
            for (T exclude : excludes) {
                this.excludes.add(exclude);
            }
        }
        return this;
    }

    public Inclusion<T> addExcludes(Collection<T> excludes) {
        if(!EmptyUtils.isEmpty(excludes)) {
            this.excludes.addAll(excludes);
        }
        return this;
    }

    public Set<T> getIncludes() {
        return Collections.unmodifiableSet(includes);
    }

    public Set<T> getExcludes() {
        return Collections.unmodifiableSet(excludes);
    }

    @Override
    public String toString() {
        return "Inclusion{" +
                "includes=" + includes +
                ", excludes=" + excludes +
                ", mode=" + mode +
                '}';
    }

}
